package com.xunji.user.config;


import com.xunji.user.util.UUIDUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
public class MinioUtil {


    @Resource
    private MinioConfiguration minioConfiguration;

    private static volatile MinioClient minioClient;

    private MinioClient getClient() {
        if (null == minioClient) {
            synchronized (MinioUtil.class) {
                if (null == minioClient) {
                    try {
                        minioClient = MinioClient.builder()
                                .endpoint(minioConfiguration.getServer())
                                .credentials(minioConfiguration.getAccessKey(), minioConfiguration.getSecretKey())
                                .build();
                    } catch (Exception e) {
                        log.error("连接minio服务异常，e->{}", e);
                    }
                }
            }
        }
        return minioClient;
    }

    /**
     * 创建bucket
     *
     * @param bucket
     * @throws Exception
     */
    private void createBucket(String bucket) throws Exception {
        getClient().makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
    }

    /**
     * 检测bucket是否存在
     *
     * @param bucket
     * @throws Exception
     */
    private void checkBucketExit(String bucket) {
        boolean bucketExit;
        try {
            bucketExit = getClient().bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
            if (!bucketExit) {
                createBucket(bucket);
            }
        } catch (Exception e) {
            log.error("找不到bucket异常，e->{}", e);
        }

    }

    /**
     * 删除bucket
     *
     * @param bucket
     * @throws Exception
     */
    private void deleteBucket(String bucket) throws Exception {
        getClient().removeBucket(RemoveBucketArgs.builder().bucket(bucket).build());
    }

    /**
     * 获取所有的bucket
     *
     * @return
     * @throws Exception
     */
    private List<Bucket> listBuckets() throws Exception {
        return getClient().listBuckets();
    }

    /**
     * 上传本地文件
     *
     * @param dirPath  文件夹名
     * @param filePath 文件路径
     * @return
     * @throws Exception
     */
    public String upload(String dirPath, String filePath) {
        checkBucketExit(minioConfiguration.getBucket());
        String fileName = UUIDUtil.getUUID() + filePath.substring(filePath.lastIndexOf("."));
        String contentType = MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        if (StringUtils.hasText(dirPath)) {
            fileName = dirPath + fileName;
        }
        try {
            getClient().uploadObject(
                    UploadObjectArgs.builder().bucket(minioConfiguration.getBucket()).object(fileName).filename(filePath).contentType(contentType).build()
            );
        } catch (Exception e) {
            log.error("上传文件失败，文件路径->{}", filePath);
            throw new RuntimeException(e);
        }
        return fileName;
    }

    /**
     * 上传文件
     *
     * @param dirPath 文件夹名
     * @param file    文件对象
     * @return
     * @throws Exception
     */
    public String uploadFile(String dirPath, MultipartFile file) {
        checkBucketExit(minioConfiguration.getBucket());
        String fileName = UUIDUtil.getUUID() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        if (StringUtils.hasText(dirPath)) {
            fileName = dirPath + "/" + fileName;
        }
        try {
            @Cleanup
            InputStream inputStream = file.getInputStream();
            getClient().putObject(PutObjectArgs.builder()
                    .bucket(minioConfiguration.getBucket())
                    .object(fileName)
                    .stream(inputStream, inputStream.available(), -1)
                    .contentType(file.getContentType())
                    .build());
        } catch (Exception e) {
            log.error("上传文件失败，e->{}", e);
        } finally {
            try {
                file.getInputStream().close();
            } catch (Exception e) {

            }
        }
        return fileName;
    }

    public String uploadBytes(String dirPath, byte[] bytes, String fileName, String contentType) {
        checkBucketExit(minioConfiguration.getBucket());
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        if (StringUtils.hasText(dirPath)) {
            fileName = dirPath + "/" + fileName;
        }
        try {
            getClient().putObject(PutObjectArgs.builder()
                    .bucket(minioConfiguration.getBucket())
                    .object(fileName)
                    .stream(inputStream, inputStream.available(), -1)
                    .contentType(contentType)
                    .build());
        } catch (Exception e) {
            log.error("上传文件失败，e->{}", e);
        }
        return fileName;
    }

    /**
     * 获取对象数据，调用方使用完后需要手动关闭InputStream
     *
     * @param object
     * @return InputStream
     */
    public InputStream getInputSteam(String object) {
        InputStream stream = null;
        try {
            stream = getClient().getObject(
                    GetObjectArgs.builder()
                            .bucket(minioConfiguration.getBucket())
                            .object(object)
                            .build());
        } catch (Exception e) {
            log.error("获取对象流失败，e->{}", e);
        }
        return stream;
    }

    public byte[] getBytes(String object) {
        InputStream stream = null;
        try {
            stream = getClient().getObject(
                    GetObjectArgs.builder()
                            .bucket(minioConfiguration.getBucket())
                            .object(object)
                            .build());
            return toByteArray(stream);
        } catch (Exception e) {
            log.error("获取对象流失败，e->{}", e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException ignored) {
                }
            }
        }
        return null;
    }

    /**
     * 下载对象到destFile文件中
     *
     * @param object
     * @param destFile
     * @throws Exception
     */
    public void downloadObject(String object, String destFile) throws Exception {
        getClient().downloadObject(
                DownloadObjectArgs.builder()
                        .bucket(minioConfiguration.getBucket())
                        .object(object)
                        .filename(destFile)
                        .build());
    }

    /**
     * 生成预览url
     *
     * @param object
     * @return String
     * @throws Exception
     */
    public String getPreviewUrl(String object) {
        if (StringUtils.hasText(object)) {
            try {
                StatObjectResponse statObjectResponse = getClient().statObject(StatObjectArgs.builder().bucket(minioConfiguration.getBucket()).object(object).build());
                if (statObjectResponse == null) return null;
                return getClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(minioConfiguration.getBucket())
                        .object(object)
                        .build());
            } catch (Exception e) {
                log.error("生成预览url失败，文件：{}不存在", object);
                return null;
            }
        }
        return null;
    }

    /**
     * 删除单个对象
     *
     * @param path
     */
    public void deleteFile(String path) {
        try {
            getClient().removeObject(RemoveObjectArgs.builder().bucket(minioConfiguration.getBucket()).object(path).build());
        } catch (Exception e) {
            log.error("删除对象文件失败失败，文件地址->{}", path);
        }
    }

    /**
     * 批量删除多个对象
     *
     * @param objects
     * @throws Exception
     */
    public void removeObjects(List<String> objects) throws Exception {
        List<DeleteObject> deleteObjects = objects.stream().map(DeleteObject::new).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results = getClient().removeObjects(RemoveObjectsArgs.builder().bucket(minioConfiguration.getBucket()).objects(deleteObjects).build());
        for (Result<DeleteError> result : results) {
            result.get();
        }
    }


    private static byte[] toByteArray(InputStream inputStream) throws Exception {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[1024];
        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        buffer.flush();
        return buffer.toByteArray();
    }
}
